import datetime
import numpy as np
import time
from scipy.stats import norm
from sklearn import svm
from stockforecaster.config import settings
from stockforecaster.lib.utils import get_next_business_date


def get_prediction(stock_quotes):
    '''
    Run the estimators, and return a prediction.
    
    @return Tuple of booleans saying whether to buy the stock short/long term respectively 
    '''
    stp = bayesian_estimator(stock_quotes)
    ltp = svm_estimator(stock_quotes)
    return (stp >= float(settings.risk_tolerance), ltp[0] > 0)
    
    
def svm_estimator(stock_quotes):
    '''
    Run SVM prediction using the given stock quotes.
    '''
    # Build a matrix of stock quote features
    data = np.matrix([(q.adjusted_close_price,) for q in stock_quotes], dtype=float)
    # Split the data into training and testing data
    train_data, test_data = _split_data(data)
    # Run everything through the predictor
    return _svm_estimator(train_data, test_data)


def bayesian_estimator(stock_quotes):
    '''
    Run Bayesian estimation using the given stock quotes.
    '''
    # Vector of timestamps
    x = np.array([time.mktime(q.date.timetuple()) for q in stock_quotes])
    # Vector of close prices
    y = np.array([q.close_price for q in stock_quotes], dtype=float)
    # Get guassian paramaters
    m, s2 = _bayesian_estimator(x, y)
    # Find the next close timestamp
    next_x = _get_next_business_timestamp(y[-1])
    # Get last close price
    last_y = y[-1]
    # Build normal distribution
    norm_dist = norm(m(next_x), s2(next_x))
    # Find the probability that the next close price is greater than the last
    prob_of_increase = 1.0 - norm_dist.cdf(last_y)    
    return prob_of_increase


def _svm_estimator(train_data, test_data):
    '''
    Return a prediction of targets given the training data history and testing 
    data for which to predict targets for.
    '''
    clf = svm.OneClassSVM(kernel='linear')
    clf.fit(train_data)
    return clf.predict(test_data)


def _bayesian_estimator(x, y, M=9, alpha=0.005, beta=11.1):
    '''
    Return a tuple of closures (mean(x), var(x)) which are the mean and
    variance of the predictive distribution inferred from the dataset, based
    on the parameters and the normal prior.
    '''
    N = len(x)
    def phi(xx):
        return np.array([(xx ** i) for i in range(M+1)])
    # formula 1.72
    aI = alpha * np.eye(M+1)
    S = np.zeros((M+1, M+1))
    for i in range(N):
        phi_xi = phi(x[i])
        S += np.outer(phi_xi, phi_xi.T)
    S = np.linalg.inv(aI + (S * beta))
    def m(xx):
        # formula 1.70
        phi_t = phi(xx).T
        sm = np.zeros(M+1)
        for i in range(N):
            sm += phi(x[i])*y[i]
        return beta * np.dot(np.dot(phi_t, S), sm)
    def s2(xx):
        # formula 1.71
        phi_x = phi(xx)
        return (1.0 / beta) + np.dot(phi_x.T, np.dot(S, phi_x))
    return (m, s2)


def _split_data(data):
    '''
    Split the data using some ratio.
    '''
    return data[:-1], data[-1:]


def _get_next_business_timestamp(ts):
    '''
    Get timestamp of the next business date.
    '''
    next_d = get_next_business_date(datetime.date.fromtimestamp(ts))
    return time.mktime(next_d.timetuple())
